import ubinascii as binascii


class Crc16(object):
    """CRC 16-bits"""
    crc_table = []
    crc_constant = 0x1021

    def __init__(self, starting_value=0xFFFF):
        self.starting_value = starting_value
        if not self.crc_table:
            self.__createCrcTable()

    def calculate(self, data):
        crc_value = self.starting_value
        for c in data:
            tmp = ((crc_value >> 8) & 0xFF) ^ c
            crc_shifted = (crc_value << 8) & 0xFF00
            crc_value = crc_shifted ^ self.crc_table[tmp]
        return crc_value

    def validate(self, data, crc_value):
        return crc_value == self.calculate(data)

    @classmethod
    def __createCrcTable(cls):
        for i in range(0, 256):
            crc = 0
            c = i << 8
            for j in range(0, 8):
                if (crc ^ c) & 0x8000:
                    crc = ((crc << 1) & 0xFFFF) ^ cls.crc_constant
                else:
                    crc = (crc << 1) & 0xFFFF
                c = (c << 1) & 0xFFFF
            cls.crc_table.append(crc)


class Tlv(object):
    """TLV communication message format.

    message format as below.
    +-----------+--------------------+-------------+
    | T         | L                  | V           |
    +===========+====================+=============+
    | Tag octes | value Length octes | Value octes |
    +-----------+--------------------+-------------+
    """

    def __init__(self, tag, value=b""):
        self.tag = tag
        self.value = value

    def __repr__(self):
        return "{}(0x{:0>4s}, value={})".format(type(self).__name__, hex(self.tag)[2:], repr(self.value))

    @property
    def length(self):
        return len(self.value)

    def toBytes(self):
        return self.tag.toBytes(2, "big") + self.length.toBytes(2, "big") + self.value

    @classmethod
    def fromBytes(cls, raw):
        tag = int.fromBytes(raw[:2], "big")
        value = raw[4:]
        return cls(tag, value=value)

    def toHex(self):
        return binascii.hexlify(self.toBytes()).decode().upper()

    @classmethod
    def fromHex(cls, string):
        return cls.fromBytes(binascii.unhexlify(string.replace(" ", "")))


class HTlv(Tlv):
    """HTLV communication message format.

    message format as below.
    +------------+-----------+--------------------+-------------+
    | H          | T         | L                  | V           |
    +============+===========+====================+=============+
    | Head octes | Tag octes | value Length octes | Value octes |
    +------------+-----------+--------------------+-------------+
    """
    HEAD = 0x24  # "$"

    def __init__(self, tag, value=b""):
        super().__init__(tag, value)

    def toBytes(self):
        return self.HEAD.toBytes(1, "big") + super().toBytes()

    @classmethod
    def fromBytes(cls, raw):
        tlv = Tlv.fromBytes(raw[1:])
        return cls(tag=tlv.tag, value=tlv.value)


class HTlvC(HTlv):
    """HTLVC communication message format.

    message format as below.
    +------------+-----------+--------------------+-------------+-----------+
    | H          | T         | L                  | V           | C         |
    +============+===========+====================+=============+===========+
    | Head octes | Tag octes | value Length octes | Value octes | CRC octes |
    +------------+-----------+--------------------+-------------+-----------+
    """
    CRC = Crc16()

    def toBytes(self):
        data = super().toBytes()
        return data + self.CRC.calculate(data).toBytes(2, "big")

    @classmethod
    def fromBytes(cls, raw):
        htlv = HTlv.fromBytes(raw[:-2])
        return cls(tag=htlv.tag, value=htlv.value)

    @classmethod
    def validate(cls, data, crc_value):
        return cls.CRC.validate(data, crc_value)


class Parser(object):

    def __init__(self):
        self.__messages = []
        self.__buffer = b""

    def clear(self):
        self.__buffer = b""

    @property
    def messages(self):
        temp, self.__messages = self.__messages, []
        return temp

    def parse(self, raw):
        self.__buffer += raw
        while True:
            head_index = self.__buffer.find(b"\x24")
            if head_index == -1:
                self.clear()
                break
            if head_index != 0:
                self.__buffer = self.__buffer[head_index:]
            if len(self.__buffer) < 7:  # H(1) + T(2) + L(2) + V(0) + C(2)
                break
            length = int.fromBytes(self.__buffer[3:5], "big")
            if len(self.__buffer) < 7 + length:
                break
            crc_value = int.fromBytes(self.__buffer[5+length:7+length], "big")
            if HTlvC.validate(self.__buffer[:5+length], crc_value):
                self.__messages.append(HTlvC.fromBytes(self.__buffer[:7+length]))
            self.__buffer = self.__buffer[7+length:]
